﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Lucene.Net.Analysis;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using RapidWebDev.Common;
using RapidWebDev.SearchIndex.Contract;

namespace RapidWebDev.SearchIndex
{
	/// <summary>
	/// The implementation to save the indexing document.
	/// </summary>
	public class SaveIndexDocumentHandler : IIndexDocumentHandler
	{
		private static readonly Analyzer defaultAnalyzer = new WhitespaceAnalyzer();
		private ILuceneAnalyzerResolver luceneAnalyzerResolver;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="luceneAnalyzerResolver"></param>
		public SaveIndexDocumentHandler(ILuceneAnalyzerResolver luceneAnalyzerResolver)
		{
			this.luceneAnalyzerResolver = luceneAnalyzerResolver;
		}

		/// <summary>
		/// Process index document for the index directory path.
		/// </summary>
		/// <param name="indexDirectoryPath"></param>
		/// <param name="indexDocMessage"></param>
		public void Process(string indexDirectoryPath, IndexDocMessage indexDocMessage)
		{
			try
			{
				System.IO.DirectoryInfo directoryInfo = null;
				if (!System.IO.Directory.Exists(indexDirectoryPath))
					directoryInfo = System.IO.Directory.CreateDirectory(indexDirectoryPath);
				else
					directoryInfo = new System.IO.DirectoryInfo(indexDirectoryPath);

				FSDirectory directory = FSDirectory.Open(directoryInfo);
				Analyzer analyzer = this.ResolveIndexWriterAnalyzer(indexDocMessage);
				IndexWriter indexWriter = new IndexWriter(directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
				Document doc = ConvertToLuceneDocument(indexDocMessage);

				Term idTerm = new Term(LuceneFields.DocumentId, indexDocMessage.Id);
				TermQuery idTermQuery = new TermQuery(idTerm);
				indexWriter.DeleteDocuments(idTermQuery);

				indexWriter.AddDocument(doc);
				indexWriter.Commit();
				indexWriter.Close();
				directory.Close();
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		private Document ConvertToLuceneDocument(IndexDocMessage indexDocMessage)
		{
			Document doc = new Document();
			doc.Add(new Field(LuceneFields.ApplicationId, indexDocMessage.ApplicationId, Field.Store.YES, Field.Index.NOT_ANALYZED));
			doc.Add(new Field(LuceneFields.DocumentType, indexDocMessage.Type, Field.Store.YES, Field.Index.NOT_ANALYZED));
			doc.Add(new Field(LuceneFields.DocumentId, indexDocMessage.Id, Field.Store.YES, Field.Index.NOT_ANALYZED));
			doc.Add(new Field(LuceneFields.StemmingLanguage, indexDocMessage.StemmingLanguage, Field.Store.YES, Field.Index.NOT_ANALYZED));
			doc.Add(new Field(LuceneFields.UpdatedOn, DateTools.DateToString(DateTime.UtcNow, DateTools.Resolution.MILLISECOND), Field.Store.YES, Field.Index.NOT_ANALYZED));

			StringBuilder contentBuilder = new StringBuilder();
			WriteDocFields(indexDocMessage, doc, contentBuilder);
			WriteDocAttachments(indexDocMessage, doc, contentBuilder);

			doc.Add(new Field(LuceneFields.Content, contentBuilder.ToString(), Field.Store.YES, Field.Index.ANALYZED));
			return doc;
		}

		private Analyzer ResolveIndexWriterAnalyzer(IndexDocMessage indexDocMessage)
		{
			Dictionary<string, Analyzer> analyzerByFields = new Dictionary<string, Analyzer>();
			analyzerByFields[LuceneFields.Content] = this.luceneAnalyzerResolver.Resolve(IndexDocFieldValueTypes.Text, indexDocMessage.StemmingLanguage);

			if (indexDocMessage.Fields != null && indexDocMessage.Fields.Count > 0)
			{
				foreach (IndexDocFieldMessage indexDocFieldMessage in indexDocMessage.Fields)
				{
					Analyzer analyzer = this.luceneAnalyzerResolver.Resolve(indexDocFieldMessage.Type, indexDocMessage.StemmingLanguage);
					if (analyzer != null)
						analyzerByFields[indexDocFieldMessage.Name] = analyzer;
				}
			}

			if (indexDocMessage.Attachments != null && indexDocMessage.Attachments.Count > 0)
			{
				for (int i = 0; i < indexDocMessage.Attachments.Count; i++)
					analyzerByFields[LuceneFields.AttachmentNameField(i)] = this.luceneAnalyzerResolver.Resolve(IndexDocFieldValueTypes.Text, indexDocMessage.StemmingLanguage);
			}

			PerFieldAnalyzerWrapper perFieldAnalyzerWrapper = new PerFieldAnalyzerWrapper(defaultAnalyzer, analyzerByFields);
			return perFieldAnalyzerWrapper;
		}

		private void WriteDocFields(IndexDocMessage indexDocMessage, Document doc, StringBuilder contentBuilder)
		{
			if (indexDocMessage.Fields != null && indexDocMessage.Fields.Count > 0)
			{
				foreach (IndexDocFieldMessage indexDocFieldMessage in indexDocMessage.Fields)
				{
					if (indexDocFieldMessage.Values == null || indexDocFieldMessage.Values.Count == 0) continue;

					IDocumentField documentField = DocumentFieldFactory.Create(indexDocFieldMessage.Type);
					if (documentField == null) continue;

					foreach (Fieldable field in documentField.GetLuceneFields(indexDocFieldMessage))
						doc.Add(field);

					if (indexDocFieldMessage.Type == IndexDocFieldValueTypes.Text)
					{
						foreach (string indexDocFieldValue in indexDocFieldMessage.Values)
						{
							if (contentBuilder.Length > 0) contentBuilder.Append(" ");
							contentBuilder.Append(indexDocFieldValue);
						}
					}
				}
			}
		}

		private static void WriteDocAttachments(IndexDocMessage indexDocMessage, Document doc, StringBuilder contentBuilder)
		{
			int attachmentsCount = 0;
			if (indexDocMessage.Attachments != null && indexDocMessage.Attachments.Count > 0)
			{
				attachmentsCount = indexDocMessage.Attachments.Count;
				for (int i = 0; i < indexDocMessage.Attachments.Count; i++)
				{
					IndexDocAttachmentMessage indexDocAttachment = indexDocMessage.Attachments[i];
					doc.Add(new Field(LuceneFields.AttachmentIdField(i), indexDocAttachment.Id, Field.Store.YES, Field.Index.NOT_ANALYZED));
					doc.Add(new Field(LuceneFields.AttachmentNameField(i), indexDocAttachment.Name, Field.Store.YES, Field.Index.ANALYZED));
					doc.Add(new Field(LuceneFields.AttachmentSharedPathField(i), indexDocAttachment.SharedPath, Field.Store.YES, Field.Index.NOT_ANALYZED));
				}
			}

			doc.Add(new Field(LuceneFields.AttachmentsCount, attachmentsCount.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NOT_ANALYZED));
		}
	}
}